using System;
using System.Reflection.Emit;

namespace Axial
{
    public class ILInstruction
    {
        public OpCode Code { get; set; }
        public object Operand { get; set; }
        public byte[] OperandData { get; set; }
        public int Offset { get; set; }

        /// <summary>
        /// Returns a friendly strign representation of this instruction
        /// </summary>
        /// <returns></returns>
        public string GetCode()
        {
            string result = "";
            result += Offset.ToString("D4") + " : " + Code;
            if (Operand != null)
            {
                switch (Code.OperandType)
                {
                    case OperandType.InlineField:
                        System.Reflection.FieldInfo field = ((System.Reflection.FieldInfo)Operand);
						result += " " + field.FieldType + " " + field.ReflectedType + "::" + field.Name + "";
                        break;
                    case OperandType.InlineMethod:
                        try
                        {
                            System.Reflection.MethodInfo method = (System.Reflection.MethodInfo)Operand;
                            result += " ";
							if (!method.IsStatic) result += "instance ";
							result += method.ReturnType + " " + method.ReflectedType + "::" + method.Name + "()";
                        }
                        catch
                        {
                            try
                            {
                                System.Reflection.ConstructorInfo mOperand = (System.Reflection.ConstructorInfo)Operand;
                                result += " ";
                                if (!mOperand.IsStatic) result += "instance ";
                                result += "void " + mOperand.ReflectedType + "::" + mOperand.Name + "()";
                            }
                            catch
                            {
                            }
                        }
                        break;
                    case OperandType.ShortInlineBrTarget:
                    case OperandType.InlineBrTarget:
                        result += " " + ((int)Operand).ToString("D4");
                        break;
                    case OperandType.InlineType:
                        result += " " + Operand;
                        break;
                    case OperandType.InlineString:
                        if (Operand.ToString() == "\r\n") result += " \"\\r\\n\"";
                        else result += " \"" + Operand.ToString() + "\"";
                        break;
                    case OperandType.ShortInlineVar:
                        result += " " + Operand.ToString();
                        break;
                    case OperandType.InlineI:
                    case OperandType.InlineI8:
                    case OperandType.InlineR:
                    case OperandType.ShortInlineI:
                    case OperandType.ShortInlineR:
						result += " " + Operand.ToString();
                        break;
                    case OperandType.InlineTok:
                        if (Operand is Type)
							result += " " + ((Type)Operand).FullName;
                        else
                            result += " not supported";
                        break;

                    default: result += "not supported"; break;
                }
            }
            return result;
        }

		public override string ToString()
		{
			return GetCode();
		}

		public bool IsStatement
		{
			get
			{
				// no pushes = yes
				if (Code.StackBehaviourPush == StackBehaviour.Push0)
					return true;
				// if it pushes something, it's no
				if (Code.StackBehaviourPush != StackBehaviour.Varpush)
					return false;
				if (Code != OpCodes.Call && Code != OpCodes.Callvirt)
					throw new NotImplementedException("dunno if the " + Code.Name + " op code is a statement");

				// constructors are not statements
				if (Operand is System.Reflection.ConstructorInfo)
					return false;
				// functions are statements if they return void
				System.Reflection.MethodInfo methodInfo = (System.Reflection.MethodInfo)Operand;
				return methodInfo.ReturnType == typeof(void);
			}
		}
    }
}
